package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author mqx
 * @date 2020-10-28 10:47:33
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Override
    public List<SeckillGoods> findAll() {
        //  key = seckill:goods
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        //  hget(key,field); field = skuId
        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
        return seckillGoods;
    }

    @Override
    public void seckillOrder(Long skuId, String userId) {
        /*
            a.	校验状态位state，
            b.	判断用户是否已经下过订单，如何控制每个用户只能下一次订单！下订单将用户存储在缓存， 执行setnx();
            c.	在缓存中做减库存操作 rpop(key);
                    如果rpop(key) 获取到的数据为空，则说明商品已经售罄，更新商品的状态位 ：
                    redisTemplate.convertAndSend("seckillpush",seckillGoods.getSkuId()+":0");
            d.	将数据保存到缓存,
            e.	更新商品的库存！
                    mysql + redis!
         */
        //  校验状态位state
        String state = (String) CacheHelper.get(skuId.toString());

        if ("0".equals(state) || StringUtils.isEmpty(state)){
            //  已售罄 或不合法
            return;
        }
        //  用户如果正常下单，那么我们需要如何记录，并且使用哪种数据类型 String ? key = xxx value = skuId
        //  如何判断用户是否已经下过订单使用setnx();
        //  userSeckillKey = seckill:user:userId value = skuId
        String userSeckillKey = RedisConst.SECKILL_USER + userId;
        //  看setnx() 是否执行通过，如果执行通过，则才放入缓存!
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(userSeckillKey, skuId.toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  判断 flag = true 表示用户第一次下单， 如果为false 表示用户已经下过订单，
        if(!flag){
            return;
        }
        //  在redis - list 中存储商品的数量进行-1;
        //  seckill:stock:skuId
        String goodsId = (String) redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();
        //  判断goodsId 是否为空！
        if (StringUtils.isEmpty(goodsId)){
            //  通知其他兄弟节点商品已经售罄！
            redisTemplate.convertAndSend("seckillpush",skuId+":0");
            //  返回
            return;
        }

        //  存储到缓存！ 缓存做hash 数据类型 key=RedisConst.SECKILL_ORDERS field = orderRecode.getUserId() value = orderRecode
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userId);
        orderRecode.setNum(1);
        orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));
        orderRecode.setOrderStr(UUID.randomUUID().toString());
        //  存储
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(userId,orderRecode);

        //更新库存
        this.updateStockCount(skuId);
    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        //  判断用户是否在缓存中存在，
        String userKey = RedisConst.SECKILL_USER + userId;
        //  seckillOrder 这个方法是预下单是存储。用户是否存在关键看setnx(); 是否执行通过！
        Boolean flag = redisTemplate.hasKey(userKey);
        if (flag){
            //  不一定！只是将用户放入缓存，看缓存中是否有过订单记录? hset(key,field,value)
            //  key = RedisConst.SECKILL_ORDERS field = userId value = orderRecode
            String orderKey = RedisConst.SECKILL_ORDERS;
            //  判断缓存中是否有过预下单
            Boolean res = redisTemplate.boundHashOps(orderKey).hasKey(userId);
            if (res){
                //  获取到缓存中预下单记录
                OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(orderKey).get(userId);
                //  返回数据
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  抢购成功，能否说明一定下过订单么? 不一定，如果用户下过订单，那么应该在缓存中有存储记录！
        //  Hash hset(key,field,value)  key = RedisConst.SECKILL_ORDERS_USERS field = userId value = orderId
        Boolean isExistOrder = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);
        //  判断是否有订单记录
        if (isExistOrder){
            String orderId = (String) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);
            //  说明已经下过订单
            return Result.build(orderId,ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //  判断状态位：
        String state = (String) CacheHelper.get(skuId.toString());

        if ("0".equals(state)){
            //  已售罄 或不合法
            return Result.build(null,ResultCodeEnum.SECKILL_FAIL);
        }
        //  默认排队中
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 更新库存，mysql + redis
     * @param skuId
     */
    private void updateStockCount(Long skuId) {
        //  获取到缓存中还剩几件商品?
        Long stockCount = redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).size();
        //  stockCount = 8 ，更新mysql + redis
        //  stockCount = 7 , 更新mysql + redis
        //  为了避免频繁更新数据库，那么可以做个缓冲！
        //  hget(key,field); field = skuId
        SeckillGoods seckillGoods = this.getSeckillGoods(skuId);
        seckillGoods.setStockCount(stockCount.intValue());
        //  数据库两次一更新
        if (stockCount%2==0){
            //  更新数据库
            seckillGoodsMapper.updateById(seckillGoods);
        }
        //  实时更新缓存！
        redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(skuId.toString(), seckillGoods);

//        if (stockCount%2==0){
//            SeckillGoods seckillGoods = this.getSeckillGoods(skuId);
//            seckillGoods.setStockCount(stockCount.intValue());
//            //  数据库两次一更新
//            //  更新数据库
//            seckillGoodsMapper.updateById(seckillGoods);
//            //  实时更新缓存！
//            redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(skuId.toString(), seckillGoods);
//        }
        //  同步谁 redis，异步谁 数据库?

    }
}
